/*
    SlideScript - minimalistic top-down scripting language.
    (C) Copyright 2014-2022 Chris Dorman - some rights reserved (GPLv2)

    View README file supplied with this software for more details
*/

#include "inc/deps.h"
#include "inc/network.h"
#include "inc/lexer.h"
#include "inc/util.h"

// Structure of file types for nethttp
// mimetype struct, searched through when checking mimetype
struct {
    char *ext;
    char *filetype;
} file_extensions [] = {
    {"gif", "image/gif"         },
    {"jpg", "image/jpeg"        },
    {"jpeg","image/jpeg"        },
    {"png", "image/png"         },
    {"zip", "application/zip"   },
    {"gz", "application/gzip"   },
    {"tar", "application/x-tar" },
    {"htm", "text/html"         },
    {"html","text/html"         },
    {"cgi", "text/cgi"          },
    {"asp","text/asp"           },
    {"xml", "text/xml"          },
    {"js", "text/js"            },
    {"css", "text/css"          },
    {"c", "text/plain"          },
    {"h", "text/plain"          },
    {"md", "text/plain"         },
    {"txt", "text/plain"        },
    {"ss",  "text/plain"        },
    {"sh", "text/plain"         },
    {"ogg", "audio/ogg"         },
    {"mp3", "audio/mpeg"        },
    {0,0} 
};

/* Serve process function, on connection catch buffer from socket */
char *snet_process_connection(int sockfd)
{
    char *buff = qmalloc(QM_SS, 1024);
    char *endbuff = qmalloc(QM_SS, 1024);
    int rr, wr, ii;
    bzero(buff, 1024);
    bzero(endbuff, 1024);

    usleep(50000);

    ii = 1;

    // read the message from client and copy it in buffer
    while ( (rr = read(sockfd, buff, 1024)) > 0)
    {
        if(ii > 1)
        {
            endbuff = qrealloc(endbuff, (strlen(endbuff) + 1024));
            //printf("%s\n", buff);
            memcpy(&endbuff[(1024 * ii) - 1024], buff, rr);
            if(rr < 1024) break;
        }
        else
        {
            memcpy(endbuff, buff, rr);
            if(rr < 1024) break;
        }

        if(rr == 1024)
        {
            ii++;
            continue;
        }
    }

    // if msg contains "Exit" then server exit, meets search, respond.
    if (strncmp("exit", buff, 4) == 0) 
    {
        wr = write(sockfd, "ss:ok", 5);
        if(wr == -1) syn_warn("ss:warn:network write error");
        syn_warn("ss:server:client called exit");
        return NULL;
    }
    else
    {
        wr = write(sockfd, "ss:ok", 5);
        if(wr == -1) syn_warn("ss:warn:network write error");

        // Get random filename for lastly pushing to save file
        char *randTokenName = malloc(17);
        FILE *socket_save = NULL;

        gen_random_string(randTokenName, 16);
        socket_save = fopen(randTokenName, "wb");

        if(strlen(endbuff) > 0)
        {
            fprintf(socket_save, "%s", endbuff);
            printf("ss:server:client buffer saved as '%s'\n", randTokenName);
            fflush(stdout);
        }

	    fclose(socket_save);
        free(randTokenName); // Free malloc, i'm done
    }

    return "ok";
}

/* Main netlisten function */
void snet_listen(int port)
{
    int sockfd, connfd, length, pid;
    struct sockaddr_in servaddr;
    struct sockaddr_in cliaddr;

    // socket create and verification
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) 
    {
        syn_error("ss:error:network socket creation failed");
    }

    bzero(&servaddr, sizeof(servaddr));

    // assign IP, PORT
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(port);

    // Binding newly created socket to given IP and verification
    if ((bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr))) != 0) 
    {
        syn_error("ss:error:network bind failed");
    }

    // Now server is ready to listen and verification
    if ((listen(sockfd, 64)) != 0) 
    {
        syn_error("ss:error:network listen failed");
    }
    else
    {
        printf("ss:server:listening on '%d'\n", port);
        fflush(stdout);
    }

    length = sizeof(cliaddr);
    // Accept the data packet from client and verification
    while ( (connfd = accept(sockfd, (struct sockaddr*)&cliaddr, (socklen_t*) &length)) )
    {
        char connIp[33];
        struct sockaddr_in* pV4Addr = (struct sockaddr_in*)&cliaddr;
        struct in_addr ipAddr = pV4Addr->sin_addr;
        if((pid = fork()) == -1)
        {
            syn_warn("ss:warn:netlisten failed to fork");
            close(connfd);
            continue;
        }
        else if(pid > 0)
        {
            close(connfd);
            continue;
        }
        else if(pid == 0)
        {
            inet_ntop(AF_INET, &ipAddr, connIp, 32);
            printf("ss:server:connection from %s\n", connIp);
            char *sc_return = snet_process_connection(connfd);
            if(sc_return == NULL)
            {
                break;
            }
            close(connfd);
        }
    }

    // After chatting close the socket
    close(sockfd);
}

// Main nettoss function
void snet_toss(char *address, int port, char *string)
{
    int sockfd;
    struct sockaddr_in servaddr;
 
    // socket create and varification
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) 
    {
        syn_warn("ss:warn:network, failed to create socket");
        return;
    }

    bzero(&servaddr, sizeof(servaddr));
  
    // assign IP, PORT
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr(address);
    servaddr.sin_port = htons(port);
  
    // connect the client socket to server socket
    if (connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) != 0) 
    {
        syn_warn("ss:warn:network, failed to connect to socket");
        close(sockfd);
        return;
    }
    else
    {
        printf("ss:client:connected to %s:%d\n", address, port);
        fflush(stdout);
    }

    // If write reports -1, exit on error.
    if(write(sockfd, string, strlen(string)) < 0)
    {
        syn_warn("ss:warn:nettoss, socket write error!");
    }

    while(sockfd != -1)
    {
        char *buf = malloc(128);
        bzero(buf, 128);

        usleep(500000);

        int listener = read(sockfd, buf, 128);

        if(listener < 0)
        {
            syn_warn("ss:warn:nettoss, server killed socket");
            free(buf);
            break;
        }
        else if(listener > 0)
        {
            if(strncmp("kill", buf, 4) == 0)
            {
                printf("ss:client: server called %s!\n", buf);
                free(buf);
                break;
            }
            else if(strncmp("ss:ok", buf, 5) == 0)
            {
                free(buf);
                fflush(stdout);
                break;
            }
            else
            {
                printf("ss:server: %s\n", buf);
                fflush(stdout);
                free(buf);
                break;
            }
        }
    }

    // close the socket after pushing content
    close(sockfd);
}

// Process connection of nethttp
void snet_http_process(int fd, int forkval)
{
    int j, file_fd, buflen, len;
    long i, filesize;
    char *fstr = NULL;
    static char buffer[HTTP_BUFSIZE+1];

    // Check to see if file is corrupted
    filesize = read(fd,buffer,HTTP_BUFSIZE); 
    if(filesize == 0 || filesize == -1) {
        sprintf(buffer,"HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n");
        if(write(fd,buffer,strlen(buffer)) < 0) syn_error("ss:net:http socket write error");
        sprintf(buffer,"<html><head><title>ss:http:missed header</title></head><body><h2>301: Missed header call</h2>" \
                   "<p>Could not locate file</p><br /><br /><center>ss:http:%s</center></body></html>", VERSION);
        if(write(fd,buffer,strlen(buffer)) < 0) syn_error("ss:net:http socket write error");
        exit(1);
    }
    
    if(filesize > 0 && filesize < HTTP_BUFSIZE) {
        buffer[filesize]=0; 
    } else {
        buffer[0]=0;
    }
    
    for(i=0;i<filesize;i++) {
        if(buffer[i] == '\r' || buffer[i] == '\n') {
            buffer[i]='*';
        }
    }

    if(strncmp(buffer,"GET ",4) && strncmp(buffer,"get ",4)) {
        sprintf(buffer,"HTTP/1.0 501 Not Implemented\r\nContent-Type: text/html\r\n\r\n");
        if(write(fd,buffer,strlen(buffer)) < 0) syn_error("ss:net:http socket write error");
        sprintf(buffer,"<html><head><title>ss:http: server not implemented</title></head><body>" \
            "<h2>501: Not Implemented</h2><p>ss:http only supports simple GET requests via HTTP/1.0</p>" \
            "<br /><br /><center>ss:http:%s</center></body></html>", VERSION);
        if(write(fd,buffer,strlen(buffer)) < 0) syn_error("ss:net:http socket write error");
        exit(1);
    }
        

    // This gets the file name from "GET /..."
    for(i=4;i<HTTP_BUFSIZE;i++) { 
        if(buffer[i] == ' ') { 
            buffer[i] = 0;
            break;
        }
    }
    
    // Check if connection is trying to view in directories behind webserver root
    for(j=0;j<i-1;j++) {
        if(buffer[j] == '.' && buffer[j+1] == '.') {
            sprintf(buffer,"HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n");
            if(write(fd,buffer,strlen(buffer)) < 0) syn_error("ss:net:http socket write error");
            sprintf(buffer,"<html><head><title>ss:http: parent directory not available</title></head><body><h2>FORBIDDEN: parent directory</h2>" \
                   "<p>You do not have access to view ../. Blocked.</p><br /><br /><center>ss:http:%s</center></body></html>", VERSION);
            if(write(fd,buffer,strlen(buffer)) < 0) syn_error("ss:net:http socket write error");
            exit(1);
        }
    }
            
    // If client is connecting with GET / (root of website) then send index
    if( !strncmp(&buffer[0],"GET /\0",6) || !strncmp(&buffer[0],"get /\0",6) ) {
        if(file_exists("index.html")) {
            strcpy(buffer,"GET /index.html");
        } else {
            sprintf(buffer,"HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n");
            if(write(fd,buffer,strlen(buffer)) < 0) syn_error("ss:net:http socket write error");
            sprintf(buffer,"<html><head><title>ss:http: file not found</title></head><body><h2>File not found</h2>" \
                   "<p>Could not locate index.html</p><br /><br /><center>ss:http:%s</center></body></html>", VERSION);
            if(write(fd,buffer,strlen(buffer)) < 0) syn_error("ss:net:http socket write error");
            exit(1);
        }
    }
    
    // Get file mimetype
    buflen=strlen(buffer);
    for(i=0;file_extensions[i].ext != 0;i++) {
        len = strlen(file_extensions[i].ext);
        if(!strncmp(&buffer[buflen-len], file_extensions[i].ext, len)) {
            fstr = file_extensions[i].filetype;
            break;
        }
    }

    if(fstr == NULL)
    {
        fstr = "application/octet-stream";
    }

    if(forkval == 0)
    {
        printf("ss:net:http request %s, %s\n", &buffer[5], fstr);
        fflush(stdout);
    }

    if(is_dir(&buffer[5]) == 1)
    {
        sprintf(buffer,"HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n");
            if(write(fd,buffer,strlen(buffer)) < 0) syn_error("ss:net:http socket write error");
            sprintf(buffer,"<html><head><title>ss:http: directory</title></head><body><h2>Directory</h2>" \
                   "<p>No directory listings at this time...</p><br /><br /><center>ss:http:%s</center></body></html>", VERSION);
            if(write(fd,buffer,strlen(buffer)) < 0) syn_error("ss:net:http socket write error");
            exit(1);
    }

    // See if the web server can open requested file from browser
    if(( file_fd = open(&buffer[5],O_RDONLY)) == -1) {
        sprintf(buffer,"HTTP/1.0 404 Not Found\r\nContent-Type: text/html\r\n\r\n");
        if(write(fd,buffer,strlen(buffer)) < 0) syn_error("ss:net:http socket write error");
        sprintf(buffer,"<html><head><title>ss:http:file not found</title></head><body><h2>404: File not found</h2>" \
                   "<p>Could not locate file</p><br /><br /><center>ss:http:%s</center></body></html>", VERSION);
        if(write(fd,buffer,strlen(buffer)) < 0) syn_error("ss:net:http socket write error");
        exit(1);
    }

    sprintf(buffer,"HTTP/1.0 200 OK\r\nContent-Type: %s\r\n\r\n", fstr);
    if(write(fd,buffer,strlen(buffer)) < 0) syn_error("ss:net:http socket write error");

    while ((filesize = read(file_fd, buffer, HTTP_BUFSIZE)) > 0) {
        if(write(fd,buffer,filesize) < 0) syn_error("ss:net:http socket write error");
    }

    
#ifdef LINUX
    sleep(1);
#endif
    exit(0);
}

// Main function for nethttp //
void snet_http(int port, int forkval)
{
    // Intergers for ports, sockets, etc
    int i, pid, listenfd, socketfd, hit;
    socklen_t length;
    // structs for socket addresses, these are always needed!
    static struct sockaddr_in cli_addr; 
    static struct sockaddr_in serv_addr;

    // This forks the httpd process into the background, if forkval > 0
    if(forkval > 0)
    {
        if(fork() != 0)
            syn_error("ss:net:http forked");

#ifndef BSD
        signal(SIGCLD, SIG_IGN);
#endif
        signal(SIGHUP, SIG_IGN); 
        for(i=0;i<32;i++) {
            close(i);   
        }
    }

    setpgid(0, 0);  
    if(forkval < 1)
    {
        printf("ss:starting httpd...\n");
        fflush(stdout);
    }

    // Check if socket succeeded to open
    if((listenfd = socket(AF_INET, SOCK_STREAM,0)) <0) {
        printf("ss:network: failed to open socket\n");
        fflush(stdout);
        exit(1);
    }

    // check if valid port number
    if(port < 0 || port >65534) {
        syn_error("ss:net:http incorrect port range (vaild 1 - 65534)");
    }

    // binds the socket to a specific port and address
    // in this case, 0.0.0.0 and the port you choose
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(port);
    if(bind(listenfd, (struct sockaddr *)&serv_addr,sizeof(serv_addr)) <0) {
        syn_error("ss:net:http failed to bind to socket");
    }
    if( listen(listenfd,64) <0) {
        syn_error("ss:net:http failed to listen to socket");
    }

    for(hit=1; ;hit++) {
        length = sizeof(cli_addr);

        // Check if server succeeded to accept connection
        if((socketfd = accept(listenfd, (struct sockaddr *)&cli_addr, &length)) < 0) {
            syn_error("ss:net:http failed to accept connection");
        }

        // Fork, and die if server fails to fork process
        if((pid = fork()) < 0) {
            syn_error("ss:net:http failed to fork connection");
        }
        else {
            if(pid == 0) {
                close(listenfd);
                snet_http_process(socketfd, forkval);
            } else {
                close(socketfd);
            }
        }
    }
}
